﻿#define FillWidthCenterVertice

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace ZXthex
{
    internal class CutAMeshCore
    {
        FillHoleHelper fill =
#if FillWidthCenterVertice
                            new FillHoleHelper_Impl1();
#else
                            new FillHoleHelper_Impl0();
#endif

        public bool CutAMesh(Mesh mesh, Transform transform, Vector3 planePointWorld, Vector3 planeNormalWorld,
                                            out Mesh pieceOne, out Mesh pieceTwo, bool needInsideUV = false)
        {

            var result = CutAMeshInternal(mesh, transform, planePointWorld, planeNormalWorld, needInsideUV);

            if (result.piece1 == null || result.piece2 == null)
            {
                pieceOne = result.piece1;
                pieceTwo = result.piece2;
                return false;
            }

            pieceOne = new Mesh();
            pieceTwo = new Mesh();

            CombineInstance[] comb = new CombineInstance[2];
            comb[0].transform = comb[1].transform = Matrix4x4.identity;
            comb[0].mesh = result.piece1;
            comb[1].mesh = result.piece1inside;
            pieceOne.CombineMeshes(comb);

            comb[0].mesh = result.piece2;
            comb[1].mesh = result.piece2inside;

            pieceTwo.CombineMeshes(comb);
            return true;
        }

        public bool CutAMesh(Mesh mesh, Transform transform, Vector3 planePointWorld, Vector3 planeNormalWorld,
                                            out Mesh pieceOne, out Mesh pieceTwo, out Mesh pieceOneInside, out Mesh pieceTwoInside,
                                                bool needInsideUV = false)
        {
            var result = CutAMeshInternal(mesh, transform, planePointWorld, planeNormalWorld, needInsideUV);

            if (result.piece1 == null || result.piece2 == null)
            {
                (pieceOne, pieceTwo, pieceOneInside, pieceTwoInside)
                            = (new Mesh(), new Mesh(), new Mesh(), new Mesh());

                return false;
            }

            (pieceOne, pieceOneInside, pieceTwo,  pieceTwoInside) = result;

            return true;
        }

        (Mesh piece1, Mesh piece1inside, Mesh piece2, Mesh piece2inside) CutAMeshInternal(Mesh mesh, Transform transform,
                                    Vector3 planePointWorld, Vector3 planeNormalWorld, bool needInsideUV = false)
        {
            const int FRONT = 0, BACK = 1;

            planeNormalWorld.Normalize();
            Vector3 planeNormalObject = transform.InverseTransformDirection(planeNormalWorld);
            planeNormalObject.Normalize();

            fill.Init(planeNormalObject);

            var vs = mesh.vertices;
            var ts = mesh.triangles;
            var ns = mesh.normals;
            var us = mesh.uv;

            List<Vector3>[] verts = { new List<Vector3>(vs), new List<Vector3>(vs) };
            List<int>[] tris = { new List<int>(ts.Length), new List<int>(ts.Length) };
            List<Vector2>[] uvs = { new List<Vector2>(us), new List<Vector2>(us) };
            List<Vector3>[] norms = { new List<Vector3>(ns), new List<Vector3>(ns) };


            var v_in_tris = new Vector3[3];
            var dots = new float[3];
            bool[] bools = new bool[3];
            for (int i = 0; i < ts.Length / 3; i++)
            {
                for (int q = 0; q < 3; q++)
                {
                    v_in_tris[q] = vs[ts[i * 3 + q]];
                    dots[q] = Vector3.Dot(transform.TransformPoint(v_in_tris[q]) - planePointWorld, planeNormalWorld);

                    if (dots[q] == 0)
                    {
                        fill.AddPoint(v_in_tris[q]);
                    }
                    bools[q] = dots[q] >= 0;
                }

                if (bools[0] && bools[1] && bools[2])
                {
                    tris[FRONT].Add(ts[i * 3]);
                    tris[FRONT].Add(ts[i * 3 + 1]);
                    tris[FRONT].Add(ts[i * 3 + 2]);
                }
                else if (!bools[0] && !bools[1] && !bools[2])
                {
                    tris[BACK].Add(ts[i * 3]);
                    tris[BACK].Add(ts[i * 3 + 1]);
                    tris[BACK].Add(ts[i * 3 + 2]);

                }
                else
                {
                    int aloneTriNum = bools[0] != bools[1] && bools[0] != bools[2] ? 0 :
                                        bools[1] != bools[0] && bools[1] != bools[2] ? 1 : 2;
                    int otherA = (aloneTriNum + 1) % 3;
                    int otherB = (aloneTriNum + 2) % 3;
                    Vector3 alone = transform.TransformPoint(vs[ts[i * 3 + aloneTriNum]]);
                    Vector3 A = transform.TransformPoint(vs[ts[i * 3 + otherA]]);
                    Vector3 B = transform.TransformPoint(vs[ts[i * 3 + otherB]]);

                    //let cutPointM = alone + m * aloneA, cutPointN = alone + n * aloneB
                    //alone + m * aloneA - planePoint   dot   normal = 0
                    //alone + n * aloneB - planePoint   dot   normal = 0
                    // m  = (aloneP dot normal ) / (aloneA dot normal)
                    // n  = (aloneP dot normal ) / (aloneB dot normal)
                    float m, n;
                    m = Vector3.Dot(planePointWorld - alone, planeNormalWorld) / Vector3.Dot(A - alone, planeNormalWorld);
                    n = Vector3.Dot(planePointWorld - alone, planeNormalWorld) / Vector3.Dot(B - alone, planeNormalWorld);
                    Vector3 M = transform.InverseTransformPoint(alone + (A - alone) * m);
                    Vector3 N = transform.InverseTransformPoint(alone + (B - alone) * n);
                    int alone_side = bools[aloneTriNum] ? FRONT : BACK;

                    int m_num = verts[alone_side].Count;
                    int n_num = verts[alone_side].Count + 1;

                    verts[alone_side].Add(M);
                    verts[alone_side].Add(N);
                    norms[alone_side].Add(Vector3.Slerp(ns[ts[i * 3 + aloneTriNum]], ns[ts[i * 3 + otherA]], m));
                    norms[alone_side].Add(Vector3.Slerp(ns[ts[i * 3 + aloneTriNum]], ns[ts[i * 3 + otherB]], n));
                    uvs[alone_side].Add(Vector3.Lerp(us[ts[i * 3 + aloneTriNum]], us[ts[i * 3 + otherA]], m));
                    uvs[alone_side].Add(Vector3.Lerp(us[ts[i * 3 + aloneTriNum]], us[ts[i * 3 + otherB]], n));
                    tris[alone_side].Add(ts[i * 3 + aloneTriNum]);
                    tris[alone_side].Add(m_num);
                    tris[alone_side].Add(n_num);



                    int anothter_side = 1 - alone_side;

                    m_num = verts[anothter_side].Count;
                    n_num = verts[anothter_side].Count + 1;
                    verts[anothter_side].Add(M);
                    verts[anothter_side].Add(N);
                    norms[anothter_side].Add(Vector3.Slerp(ns[ts[i * 3 + aloneTriNum]], ns[ts[i * 3 + otherA]], m));
                    norms[anothter_side].Add(Vector3.Slerp(ns[ts[i * 3 + aloneTriNum]], ns[ts[i * 3 + otherB]], n));
                    uvs[anothter_side].Add(Vector3.Lerp(us[ts[i * 3 + aloneTriNum]], us[ts[i * 3 + otherA]], m));
                    uvs[anothter_side].Add(Vector3.Lerp(us[ts[i * 3 + aloneTriNum]], us[ts[i * 3 + otherB]], n));
                    tris[anothter_side].Add(ts[i * 3 + otherA]);
                    tris[anothter_side].Add(n_num);
                    tris[anothter_side].Add(m_num);
                    tris[anothter_side].Add(ts[i * 3 + otherB]);
                    tris[anothter_side].Add(n_num);
                    tris[anothter_side].Add(ts[i * 3 + otherA]);

                    fill.AddPoint(M);
                    fill.AddPoint(N);
                }
            }


            for (int i = 0; i < 2; i++)
            {
                RemoveUnusedVertices(verts[i], tris[i], norms[i], uvs[i]);
            }


            if (tris[FRONT].Count == 0)
            {
                return (null, null, mesh, null);
            }
            if (tris[BACK].Count == 0)
            {
                return (mesh, null, null, null);
            }

            var p1 = new Mesh();
            var p2 = new Mesh();
            p1.vertices = verts[FRONT].ToArray();
            p1.triangles = tris[FRONT].ToArray();
            p1.normals = norms[FRONT].ToArray();
            p1.uv = uvs[FRONT].ToArray();


            p2.vertices = verts[BACK].ToArray();
            p2.triangles = tris[BACK].ToArray();
            p2.normals = norms[BACK].ToArray();
            p2.uv = uvs[BACK].ToArray();


            var holes = fill.GetMeshes(needInsideUV);

            return (p1, holes[0], p2, holes[1]);
        }


        void RemoveUnusedVertices(List<Vector3> verts, List<int> tris, params IList[] lists)
        {
            for (int i = 0; i < verts.Count; i++)
            {
                if (!tris.Contains(i))
                {
                    for (int j = 0; j < tris.Count; j++)
                    {
                        if (tris[j] > i) tris[j] -= 1;
                    }
                    verts.RemoveAt(i);
                    for (int j = 0; j < lists.Length; j++)
                    {
                        lists[j].RemoveAt(i);
                    }


                    i--;
                }
            }
        }

    }
}